from typing import Callable
from extensions.ext_redis import redis_client
import functools
import json
import logging
import pickle


def cache_result(
    keys: list[str] = [],
    expire: int = 60,#sec
    serializer: str = "json",
    version: str = "v1",
    condition: Callable|None = None,
):
    """
    A decorator for caching function results in Redis, supporting attribute-based keys.
    缓存函数的结果，支持多种输入输出类型，并可以使用参数的属性作为键值。
    :param keys: List of keys to build the Redis cache key. Supports 'arg_name' or 'arg_name.attr' format.
    :param expire: Expiration time in seconds for the cache.
    :param serializer: 序列化方式 ('json' 或 'pickle')
    :param version: 缓存版本号，用于强制更新缓存
    :param condition: 缓存条件函数，返回True时才会缓存结果
    """

    def decorator(func):
        def resolve_key(key: str, args: tuple, kwargs: dict) -> str:
            """
            Resolve a key from function arguments. Supports attribute-based keys like 'user.id'.
            """
            if "." in key:  # Handle attribute-based keys
                base_key, attr = key.split(".", 1)
                if base_key in kwargs:
                    value = kwargs[base_key]
                else:
                    # Positional argument handling
                    arg_names = func.__code__.co_varnames[: func.__code__.co_argcount]
                    if base_key in arg_names:
                        index = arg_names.index(base_key)
                        value = args[index]
                    else:
                        raise ValueError(f"Key '{base_key}' not found in arguments.")
                # Retrieve the attribute value
                if value:
                    for attr_part in attr.split("."):
                        value = getattr(value, attr_part, None)
                        if value is None:
                            value = "None"
                            # raise ValueError(
                            # f"Attribute '{attr}' not found on '{base_key}'.")

                else:
                    value = "None"
                return str(value)
            elif key in kwargs:
                return str(kwargs[key])
            else:
                # Positional argument handling
                arg_names = func.__code__.co_varnames[: func.__code__.co_argcount]
                if key in arg_names:
                    index = arg_names.index(key)
                    return str(args[index]) if len(args) > index else "None"
                raise ValueError(f"Key '{key}' not found in arguments.")

        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 检查缓存条件
            # logging.info(
            #     f"检查缓存条件: {str([str(arg) for arg in args])}:condition={condition(*args, **kwargs)}"
            # )
            if condition and not condition(*args, **kwargs):
                # logging.info(f"缓存条件不满足，不缓存结果: {func.__name__}")
                return func(*args, **kwargs)
            # logging.info(f"缓存函数: {func.__name__}")

            # Build Redis key using specified keys
            if not keys:
                redis_key = f"{func.__name__}:{version}:"
            else:
                redis_key = f"{func.__name__}:{version}:" + ":".join(
                    resolve_key(key, args, kwargs) for key in keys
                )
                # logging.info(f"Redis key: {redis_key}")

            # Check if the result is cached
            if redis_client.exists(redis_key):
                cached_response: bytes = redis_client.get(redis_key)
                try:
                    if serializer == "json":
                        return json.loads(cached_response.decode("utf-8"))
                    elif serializer == "pickle":
                        return pickle.loads(cached_response)
                    else:
                        raise ValueError("不支持的序列化器")
                except Exception as e:
                    print(f"反序列化失败: {e}")
                    # 如果反序列化失败，则继续执行函数并更新缓存

            # Call the function and cache the result
            result = func(*args, **kwargs)

            # 将结果序列化后存入缓存
            try:
                if result and (not condition or condition(*args, **kwargs)):

                    serialized_result = (
                        json.dumps(result, ensure_ascii=False).encode("utf-8")
                        if serializer == "json"
                        else pickle.dumps(result)
                    )
                    redis_client.setex(redis_key, expire, serialized_result)
            except Exception as e:
                logging.error(f"序列化失败: {e}")
            return result

        return wrapper

    return decorator


def clear_cache(func_name: str, keys: list[str]|None = None, version: str = "v1"):
    """
    清除指定函数的缓存
    :param func_name: 函数名称
    :param keys: 缓存键列表，如果为None则清除该函数的所有缓存
    :param version: 缓存版本号
    """
    if keys:
        if isinstance(keys, str):
            keys = [keys]
        redis_key = f"{func_name}:{version}:" + ":".join(str(key) for key in keys)
        redis_client.delete(redis_key)
    else:
        pattern = f"{func_name}:{version}:*"
        for key in redis_client.keys(pattern):
            redis_client.delete(key)


def cache_hit_rate(func_name: str, version: str = "v1") -> float:
    """
    获取缓存命中率
    :param func_name: 函数名称
    :param version: 缓存版本号
    :return: 缓存命中率 (0.0 - 1.0)
    """
    pattern = f"{func_name}:{version}:*"
    keys = redis_client.keys(pattern)
    if not keys:
        return 0.0

    total = 0
    hits = 0
    for key in keys:
        total += 1
        if redis_client.exists(key):
            hits += 1
    return hits / total
